﻿using System;
using System.IO;
using System.Threading;
using System.Web;
using GatewayUtil.RequestResponse;
using jade.wrapper;
using log4net;
using jade.content;
using System.Web.Services.Protocols;
using System.Collections;
using jade.core;
using AgentContainer = jade.wrapper.AgentContainer;
using System.ServiceModel.Channels;
using GatewayUtil.gatewayConfig;
using Web.soap;

namespace Web.handler
{
    /// <summary>
    /// Handler responsible for incomming SOAP processing. 
    /// All incomming SOAP messages puts to GWAgent queue
    /// </summary>
    public class GWHandler : IHttpHandler
    {
        
        private static readonly ILog log = LogManager.GetLogger(typeof(GWHandler));
        private static AgentContainer container = null;
        public static AgentController gw = null;
        private static Hashtable wsStore = null;
        private SoapProcessor soapProcessor = new SoapProcessor(MessageVersion.Soap12WSAddressing10);
        
        public bool IsReusable
        {
            get { return false; }
        }

        public void init(HttpContext context)
        {

            if (wsStore == null)
            {
                log.Debug("getting the wsStore");
                wsStore = (Hashtable)context.Application.Get("wsStore");
                if (wsStore == null)
                {
                    log.Error("WebService List reference in the ApplicationState is null");
                    throw new SoapException("Internal error. WebService List doesn't exist", SoapException.ServerFaultCode);
                }
            }

            if (wsStore.Count == 0)
            {
                log.Error("WebService List reference in the ApplicationState is null");
                throw new SoapException("Internal error. WebService List not prepared yet", SoapException.ServerFaultCode);
            }

            
            if (container == null)
            {
                log.Debug("getting the AgentContainer");
                container = (AgentContainer)context.Application.Get("container");
                if (container == null)
                {
                    log.Error("AgentContainer reference in the ApplicationState is null");
                    throw new SoapException("Internal error. AgentContainer doesn't exist", SoapException.ServerFaultCode);
                }             
            }

            if (gw == null)
            {
                log.Debug("getting the GatewayAgent");
                gw = (AgentController)context.Application.Get("gwAgent");
                if (container == null)
                {
                    log.Error("GatewayAgent reference in the ApplicationState is null");
                    throw new SoapException("Internal error. GatewayAgent is not present in the MAS", SoapException.ServerFaultCode);
                }
            }

        }

        public void ProcessRequest(HttpContext context)
        {
            log.Debug("New SOAP request");
            MemoryStream outputStream = null;
            String soapAction = null;
            try
            {
                init(context);
                soapAction = this.soapProcessor.getSoapAction(context.Request);

                // parse agentName from request
                // example:  /calculator.agent  ->  agentName is "calculator"
                string agentName = getAgentName(context);
                //String agentName = context.Request.RawUrl.Substring(1, context.Request.Path.Length - 7);
                AgentDescription agentDesc = getAgentDescription(agentName);

                checkAgent(agentDesc, agentName, soapAction);
                IDataObject dataObj = getDataObject(context.Request.InputStream, agentDesc, soapAction, agentName);
                sendRequestToAgent(dataObj);
                dataObj = getAgentResponse(dataObj);
                outputStream = serializeObject(dataObj.getResponse(), soapAction);
            }
            catch (Exception exception)
            {
                outputStream = soapProcessor.processSoapFault(exception, soapAction);
            }
            finally
            {
                log.Debug("Request processing finished");
                sendResponse(context, outputStream);
            }
        }

        private string getAgentName(HttpContext context)
        {
            // remove ~/ and final suffix .agent from appRelativeCurrentExecutionFilePath in order to ger a real name of an agent
            return context.Request.AppRelativeCurrentExecutionFilePath.Remove(0,2).Replace(".agent","");
            
            //return null;
        }


        private IDataObject getDataObject(Stream httpStream, AgentDescription agentDesc, String soapAction, String agentName)
        {
            OperationDescription operation = agentDesc.operations[soapAction];
            Object requestObj = soapProcessor.deserializeSoapMessage(httpStream, operation.assemblyName, operation.operationName);

            if (!(requestObj is AgentAction))
            {
                log.Error("Request object is not a AgentAction, its fullname is " + requestObj.GetType().FullName);
                throw new SoapException("Unknown error, request object is not a AgentAction", SoapException.ServerFaultCode);
            }

            IDataObject dataObj = new DataObject();
            Request req = new Request();
            req.setRequest((AgentAction)requestObj);
            dataObj.setOntology(operation.ontology);
            req.setReceiver(new AID(agentName, false));
            dataObj.setRequest(req);
            return dataObj;
        }


        private void sendRequestToAgent(IDataObject dataObj)
        {
            log.Debug("putting request to the GatewayAgent");
            gw.putO2AObject(dataObj, false);
            
            gw.activate();

        }

        private IDataObject getAgentResponse(IDataObject dataObj)
        {
            log.Debug("waiting for releasing request");
            // wait from notify by GatewayAgent OR given time
            lock (dataObj) Monitor.Wait(dataObj, 55000);
            log.Debug("request released");
            return dataObj;

        }

        private MemoryStream serializeObject(IResponse response, String soapAction)
        {
            MemoryStream outputStream = soapProcessor.serialize(response, soapAction);
            return outputStream;
        }

        private void sendResponse(HttpContext context, MemoryStream outputStream)
        {
            context.Response.ContentType = "text/xml";
            outputStream.WriteTo(context.Response.OutputStream);
            context.Response.OutputStream.Flush();
            log.Debug("Response sent");
        }

        /// <summary>
        /// Check if:
        /// - agent is publicable by WebService
        /// - agent is present in the MAS
        /// </summary>
        /// <param name="agentDesc">AgentDescription of given agent, could be null (it means that agent is not present in the mas)</param>
        /// <param name="agentName"></param>
        private void checkAgent(AgentDescription agentDesc, String agentName, String soapAction)
        {
            if (agentDesc == null)
            {
                throw new SoapException("Agent '"+agentName+"' doesn't exist in the MAS", SoapException.ClientFaultCode);
            }
            if (!agentDesc.publicable)
            {
                throw new SoapException("Agent is not set to being publicable", SoapException.ClientFaultCode);
            }
            if (!agentDesc.operations.ContainsKey(soapAction))
            {
                throw new SoapException("Requested operation '" + soapAction + "' is not provided by agent '" + agentName + "'", SoapException.ClientFaultCode);
            }
        }

        /// <summary>
        /// Get Agent Description from internal WS Store
        /// </summary>
        /// <param name="agentName"></param>
        /// <returns>it could be null</returns>
        private AgentDescription getAgentDescription(string agentName)
        {
            Object agentDesc = null;
          
            // get AgentDesciption for given agent
            agentDesc = wsStore[agentName];
            
            if (agentDesc is AgentDescription)
            {
                return (AgentDescription)agentDesc;
            }else return null;
        }


    }

    }

